#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "driver/gpio.h"
#include "esp_log.h"

#include "key.h"
#include "event.h"

#define KEY_NUM				3

#define GPIO_INPUT_PIN_SEL  ((1ULL<<KEY_LEFT_GPIO) | (1ULL<<KEY_RIGHT_GPIO) | (1ULL<<KEY_ENTER_GPIO))
#define ESP_INTR_FLAG_DEFAULT 0

#define PRESS_FLAG			(1<<0)
#define RELEASE_FLAG		(1<<4)
#define ALL_FLAG			(PRESS_FLAG | RELEASE_FLAG)

struct key_state {
	uint8_t key_flag;
	uint32_t backup_time;
} k_st[KEY_NUM];

static xQueueHandle gpio_evt_queue = NULL;

static void IRAM_ATTR gpio_isr_handler(void* arg)
{
    uint32_t gpio_num = (uint32_t) arg;
	uint32_t value = gpio_num << 1;
	if (gpio_get_level(gpio_num))
		value |= 1;
    xQueueSendFromISR(gpio_evt_queue, &value, NULL);
}

static void key_handle(void* arg)
{
	uint32_t value;
	uint32_t gpio_num;
	uint8_t press;
	struct key_state *p_k_st = NULL;
	struct event_struct key_evt = {0};

	for(;;) {
		if(xQueueReceive(gpio_evt_queue, &value, portMAX_DELAY)) {
			gpio_num = value >> 1;
			p_k_st = &k_st[gpio_num - KEY_LEFT_GPIO];
			if ((value & 0x01) == 0) {
				p_k_st->key_flag |= PRESS_FLAG;
				p_k_st->backup_time = esp_log_timestamp();
			} else if (p_k_st->key_flag & PRESS_FLAG) {
				p_k_st->key_flag |= RELEASE_FLAG;
				p_k_st->backup_time = esp_log_timestamp() - p_k_st->backup_time;
			}

			if (p_k_st->key_flag == ALL_FLAG) {
				p_k_st->key_flag = 0;

				if (p_k_st->backup_time > 500) {
					printf("%u long release!\n", gpio_num);
					press = 1;
				} else if (p_k_st->backup_time > 80) {
					printf("%u short release!\n", gpio_num);
					press = 0;
				} else {
					/* Ignore this situation, possibly due to jitter */
					continue;
				}

				value = gpio_num << 1;
				if (press)
					value += 1;
				memset(&key_evt, 0, sizeof(key_evt));
				key_evt.event_type = KEY_EVENTS << 8;
				key_evt.key_s.key_num = gpio_num;
				if (press)
					key_evt.key_s.key_press = 1;

				xQueueSend(evt_queue, &key_evt, portMAX_DELAY);
			}
		}
	}
}

void key_init(void)
{
    memset(k_st, 0, sizeof(k_st[0]) * KEY_NUM);
    //zero-initialize the config structure.
    gpio_config_t io_conf = {0};
    //interrupt of falling edge
    io_conf.intr_type = GPIO_INTR_ANYEDGE;
    //bit mask of the pins
    io_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL;
    //set as input mode
    io_conf.mode = GPIO_MODE_INPUT;
    //enable pull-up mode
    io_conf.pull_up_en = 0;
    io_conf.pull_down_en = 0;
    gpio_config(&io_conf);

    //create a queue to handle gpio event from isr
    gpio_evt_queue = xQueueCreate(5, sizeof(uint32_t));
    //start gpio task
    xTaskCreate(key_handle, "key_handle_func", 2048, NULL, 10, NULL);

    //install gpio isr service
    gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
    //hook isr handler for specific gpio pin
    gpio_isr_handler_add(KEY_LEFT_GPIO, gpio_isr_handler, (void*) KEY_LEFT_GPIO);
    //hook isr handler for specific gpio pin
    gpio_isr_handler_add(KEY_RIGHT_GPIO, gpio_isr_handler, (void*) KEY_RIGHT_GPIO);
    //hook isr handler for specific gpio pin
    gpio_isr_handler_add(KEY_ENTER_GPIO, gpio_isr_handler, (void*) KEY_ENTER_GPIO);
}
